/*
    hdvb - Framework for sophisticated DVB applications
    Copyright (C) 2012 Tony Houghton <h@realh.co.uk>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

/* Scans for channels and adds them to a database */

#include <glib.h>
#include <glib/gstdio.h>

#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <fcntl.h>
#include <linux/dvb/dmx.h>

#include <sqlite3.h>

#include <hdvb/alloc-lcns.h>
#include <hdvb/cacheddb.h>
#include <hdvb/chanscantodb.h>
#include <hdvb/chanscan-factory.h>
#include <hdvb/chansdb.h>
#include <hdvb/codes.h>
#include <hdvb/dbcommon.h>
#include <hdvb/freesatscantodb.h>
#include <hdvb/hdvb-receiver.h>
#include <hdvb/secfilter.h>
#include <hdvb/section.h>
#include <hdvb/tuning.h>

static int adapter = 0;
static int frontend = 0;
static char *db_filename = NULL;
static char *db_new_filename = NULL;
static gboolean freeview = FALSE;
static gboolean freesat = FALSE;
static gboolean dump_channels = FALSE;
static gboolean dump_vdr = FALSE;
static char *dump_filename = NULL;
static char *region_name = NULL;
static char *factory = NULL;

#define SPACE "\t\t\t      "

static GOptionEntry entries[] = {
    { "adapter", 'a', 0, G_OPTION_ARG_INT, &adapter, "Adapter number A", "A" },
    { "frontend", 'f', 0, G_OPTION_ARG_INT, &frontend,
            "Front-end number F", "F" },
    { "database", 'd', 0, G_OPTION_ARG_FILENAME, &db_filename,
            "Write to database/channels.conf FILE (compulsory).\n"
            SPACE "If -c/--channels-conf is used, FILE may be &N\n"
            SPACE "where N is the number of a file descriptor",
            "FILE" },
    { "factory", 't', 0, G_OPTION_ARG_STRING, &factory,
            "Name of factory for creating scanner object:\n"
            SPACE "default, Freesat or Freeview",
            "FACTORY" },
    { "vdr-channels-conf", 'v', 0, G_OPTION_ARG_NONE, &dump_vdr,
            "Dump channels to FILE in VDR format" },
    { "channels-conf", 'c', 0, G_OPTION_ARG_NONE, &dump_channels,
            "Dump channels to FILE in channels.conf format for\n"
            SPACE "other apps" },
    { "region", 'r', 0, G_OPTION_ARG_STRING, &region_name,
            "eg 'South/Meridian S'",
            "REGION" },
    { NULL },
};

static GMainLoop *main_loop;

typedef struct {
    char *tuning_str;
    int ts_id;
    GList *services;    /* each link->data is cast from service_id */
    int n_services;
} PidHarvestTS;

typedef struct {
    HdvbReceiver *rcv;
    sqlite3 *db;
    sqlite3_stmt *stmt;
    GList *transports;
    GList *current;
    HdvbCachedDb *cache;
} PidHarvestData;

static const char *
code_rate_to_vdr(fe_code_rate_t r)
{
    switch (r)
    {
        case FEC_1_2:
            return "12";
        case FEC_2_3:
            return "23";
        case FEC_3_4:
            return "34";
        case FEC_4_5:
            return "45";
        case FEC_5_6:
            return "56";
        case FEC_6_7:
            return "67";
        case FEC_7_8:
            return "78";
        case FEC_8_9:
            return "89";
        case FEC_3_5:
            return "35";
        case FEC_9_10:
            return "910";
        default:
            break;
    }
    return "0";
}

static const char *
modulation_to_vdr(fe_modulation_t m)
{
    switch (m)
    {
        case QAM_16:
            return "16";
        case QAM_32:
            return "32";
        case QAM_64:
            return "64";
        case QAM_128:
            return "128";
        case QAM_256:
            return "256";
        case QPSK:
            return "2";
        case PSK_8:
            return "5";
        case APSK_16:
            return "6";
        case VSB_8:
            return "10";
        case VSB_16:
            return "11";
        case DQPSK:
            return "12";
        default:
            break;
    }
    return "999";
}

static int
bandwidth_to_vdr(guint32 b)
{
    if (b == 1712000)
        return 1712;
    else
        return b / 1000000;
}

static int
transm_to_vdr(fe_transmit_mode_t t)
{
    switch (t)
    {
        case TRANSMISSION_MODE_2K:
            return 2;
        case TRANSMISSION_MODE_8K:
            return 8;
        case TRANSMISSION_MODE_4K:
            return 4;
        case TRANSMISSION_MODE_16K:
            return 16;
        case TRANSMISSION_MODE_32K:
            return 32;
        default:
            break;
    }
    return 0;
}

static int
guard_to_vdr(fe_guard_interval_t i)
{
    switch (i)
    {
        case GUARD_INTERVAL_1_32:
            return 32;
        case GUARD_INTERVAL_1_16:
            return 16;
        case GUARD_INTERVAL_1_8:
            return 8;
        case GUARD_INTERVAL_1_4:
            return 4;
        case GUARD_INTERVAL_1_128:
            return 128;
        case GUARD_INTERVAL_19_128:
            return 19128;
        case GUARD_INTERVAL_19_256:
            return 19256;
        default:
            break;
    }
    return 0;
}

static int
hier_to_vdr(fe_hierarchy_t h)
{
    switch (h)
    {
        case HIERARCHY_1:
            return 1;
        case HIERARCHY_2:
            return 1;
        case HIERARCHY_4:
            return 4;
        default:
            break;
    }
    return 0;
}

static const char *
roll_off_to_vdr(guint32 val)
{
    switch (val)
    {
        case ROLLOFF_25:
            return "25";
            break;
        case ROLLOFF_20:
            return "20";
            break;
    }
    return NULL;
}

static guint32
find_prop(HdvbTuningData *data, guint32 prop, guint32 deflt)
{
    guint32 n;

    for (n = 0; n < data->pprops.num; ++n)
    {
        if (data->props[n].cmd == prop)
            return data->props[n].u.data;
    }
    return deflt;
}

static void
print_channels(sqlite3 *sql)
{
    sqlite3_stmt *chan_stmt = hdvb_db_prepare_statement(sql,
            "SELECT s.lcn, s.service_name, t.tuning_str, s.free_ca_mode,"
            "       s.service_id, t.network_id, t.ts_id, s.provider_name"
            "   FROM services AS s, transports AS t"
            "   WHERE s.ts_id = t.ts_id"
            "   ORDER BY s.lcn, s.service_name");
    sqlite3_stmt *pid_stmt = hdvb_db_prepare_statement(sql,
            "SELECT pid, stream_type, lang, audio_type"
            "   FROM pids WHERE service_id = ? AND ts_id = ?");
    int last_lcn = -1;
    FILE *fp = (dump_filename[0] == '&') ?
            fdopen(atoi(dump_filename + 1), "w") :
            fopen(dump_filename, "w");

    if (!fp)
    {
        fprintf(stderr, "Unable to open output file: %s", strerror(errno));
    }
    if (!chan_stmt || !pid_stmt || !fp)
    {
        g_main_loop_quit(main_loop);
        return;
    }
    while (hdvb_db_step(chan_stmt) == SQLITE_ROW)
    {
        HdvbTuningData td;
        int lcn = sqlite3_column_int(chan_stmt, 0);
        int service_id = sqlite3_column_int(chan_stmt, 4);
        int ts_id = sqlite3_column_int(chan_stmt, 6);
        int vpid = 0;
        int tpid = 0;
        int apid = 0;
        int dpid = 0;
        int apid_undef = 0;
        int dpid_undef = 0;
        GList *mpeg_audio = NULL;
        GList *dolby_audio = NULL;
        GList *link;
        char *params = NULL;
        const char *tuning_str = (const char *)
                sqlite3_column_text(chan_stmt, 2);
        char **tuning_split = g_strsplit(tuning_str, " ", 0);
        int freq = 0;
        guint32 srate = 0;
        GString *audio_s = NULL;
        gboolean prev_dolby = FALSE;
        char *audio_s0 = NULL;
        const char *service_name;

        hdvb_tuning_string_to_data(tuning_str, &td);
        switch (td.type)
        {
            case FE_QPSK:
                freq = atoi(tuning_split[1]) / 1000;
                if (dump_vdr)
                {
                    guint32 roll_off =
                            find_prop(&td, DTV_ROLLOFF, G_MAXUINT32);
                    const char *roll_off_s = (roll_off == G_MAXUINT32) ? NULL :
                            roll_off_to_vdr(roll_off);
                    gboolean dvb_s2 = tuning_split[5] != NULL &&
                            tuning_split[0][1] != '1';

                    params = g_strdup_printf("%cC%sM%sO%sS%c",
                            toupper(tuning_split[2][0]),
                            code_rate_to_vdr(
                                    find_prop(&td, DTV_INNER_FEC, FEC_AUTO)),
                            dvb_s2 ? modulation_to_vdr(
                                    find_prop(&td, DTV_MODULATION, QPSK)) : "2",
                            (roll_off_s) ? roll_off_s : "0",
                            dvb_s2 ? '1' : '0');
                    srate = find_prop(&td, DTV_SYMBOL_RATE, 27500000) / 1000;
                }
                else
                {
                    params = g_strdup_printf("%c:0:%d",
                            tolower(tuning_split[2][0]),
                            find_prop(&td, DTV_SYMBOL_RATE, 27500000) / 1000);
                }
                break;
            case FE_OFDM:
                freq = atoi(tuning_split[1]);
                if (dump_vdr)
                {
                    char t2[16];
                    guint32 plp_id;

                    plp_id = find_prop(&td, DTV_STREAM_ID, 0xffffffff);
                    if (plp_id != 0xffffffff)
                        snprintf(t2, 16, "S1P%d", plp_id);
                    else
                        t2[0] = 0;
                    params = g_strdup_printf("I0C%sD%sM%sB%dT%dG%dY%d%s",
                            code_rate_to_vdr(
                                    find_prop(&td, DTV_CODE_RATE_HP, FEC_AUTO)),
                            code_rate_to_vdr(
                                    find_prop(&td, DTV_CODE_RATE_LP, FEC_AUTO)),
                            modulation_to_vdr(
                                    find_prop(&td, DTV_MODULATION, QAM_AUTO)),
                            bandwidth_to_vdr(
                                    find_prop(&td, DTV_BANDWIDTH_HZ, 8000000)),
                            transm_to_vdr(
                                    find_prop(&td, DTV_TRANSMISSION_MODE,
                                            TRANSMISSION_MODE_AUTO)),
                            guard_to_vdr(
                                    find_prop(&td, DTV_GUARD_INTERVAL,
                                            GUARD_INTERVAL_AUTO)),
                            hier_to_vdr(
                                    find_prop(&td, DTV_HIERARCHY,
                                            HIERARCHY_AUTO)),
                            t2);
                    srate = 27500;
                }
                else
                {
                    const char *fec_hp =
                            code_rate_to_vdr(
                                    find_prop(&td, DTV_CODE_RATE_HP, FEC_AUTO));
                    const char *fec_lp =
                            code_rate_to_vdr(
                                    find_prop(&td, DTV_CODE_RATE_LP, FEC_AUTO));
                    const char *constel = modulation_to_vdr(
                            find_prop(&td, DTV_MODULATION, QAM_AUTO));
                    int hier = hier_to_vdr(
                            find_prop(&td, DTV_HIERARCHY, HIERARCHY_AUTO));
                    char *hier_s = hier ? g_strdup_printf("%d", hier) :
                            g_strdup("NONE");

                    params = g_strdup_printf("INVERSION_AUTO:BANDWIDTH_%d_MHZ:"
                            "%c_%s:%c_%s:%s%s:TRANSMISSION_MODE_%d_K:"
                            "GUARD_INTERVAL_1_%d:HIERARCHY_%s",
                            bandwidth_to_vdr(
                                    find_prop(&td, DTV_BANDWIDTH_HZ, 8000000)),
                            fec_hp[0], fec_hp + 1, fec_lp[0], fec_lp + 1,
                            isdigit(constel[0]) ? "QAM_" : "", constel,
                            transm_to_vdr(
                                    find_prop(&td, DTV_TRANSMISSION_MODE,
                                            TRANSMISSION_MODE_AUTO)),
                            guard_to_vdr(
                                    find_prop(&td, DTV_GUARD_INTERVAL,
                                            GUARD_INTERVAL_AUTO)),
                            hier_s);
                    g_free(hier_s);
                }
                break;
            default:
                /* FIXME: Support QAM and ATSC one day */
                break;
        }

        sqlite3_bind_int(pid_stmt, 1, service_id);
        sqlite3_bind_int(pid_stmt, 2, ts_id);
        while (hdvb_db_step(pid_stmt) == SQLITE_ROW)
        {
            int pid = sqlite3_column_int(pid_stmt, 0);
            int stream_type = sqlite3_column_int(pid_stmt, 1);
            guint32 lang = sqlite3_column_int(pid_stmt, 2);
            const char *lang_s = (const char *) &lang;

            if (HDVB_STREAM_TYPE_IS_VIDEO(stream_type))
            {
                vpid = pid;
            }
            else if (HDVB_STREAM_TYPE_IS_AUDIO(stream_type))
            {
                gboolean undef =
                        (sqlite3_column_int(pid_stmt, 3) ==
                            HDVB_AUDIO_TYPE_UNDEFINED);
                gboolean mpeg = HDVB_STREAM_TYPE_IS_MPEG_AUDIO(stream_type);

                if (dump_vdr)
                {
                    char *s = lang ? g_strdup_printf("%d=%s", pid, lang_s) :
                            g_strdup_printf("%d", pid);
                    GList *(*adder)(GList *, gpointer) = undef ?
                            g_list_prepend : g_list_append;

                    if (HDVB_STREAM_TYPE_IS_MPEG_AUDIO(stream_type))
                        mpeg_audio = adder(mpeg_audio, s);
                    else
                        dolby_audio = adder(dolby_audio, s);
                }
                else
                {
                    if (undef && mpeg && !apid_undef)
                        apid_undef = pid;
                    else if (undef && !mpeg && !dpid_undef)
                        dpid_undef = pid;
                    else if (!undef && mpeg && !apid)
                        apid = pid;
                    else if (!undef && !mpeg && !dpid)
                        dpid = pid;
                }
            }
            else if (stream_type == HDVB_STREAM_TYPE_PRIVATE_PES)
            {
                tpid = pid;
            }
        }
        sqlite3_reset(pid_stmt);
        sqlite3_clear_bindings(pid_stmt);

        service_name = (const char *) sqlite3_column_text(chan_stmt, 1);
        if (dump_vdr)
        {
            const char *provider_name;
            char *full_name;

            for (link = mpeg_audio; link; link = g_list_next(link))
            {
                if (audio_s)
                {
                    g_string_append_printf(audio_s, ",%s",
                            (const char *) link->data);
                }
                else
                {
                    audio_s = g_string_new(link->data);
                }
                g_free(link->data);
            }
            if (mpeg_audio)
                g_list_free(mpeg_audio);
            if (dolby_audio)
            {
                if (audio_s)
                    g_string_append_c(audio_s, ';');
                else
                    audio_s = g_string_new("");
            }
            for (link = dolby_audio; link; link = g_list_next(link))
            {
                if (prev_dolby)
                {
                    g_string_append_printf(audio_s, ",%s",
                            (const char *) link->data);
                }
                else
                {
                    g_string_append(audio_s, link->data);
                    prev_dolby = TRUE;
                }
                g_free(link->data);
            }
            if (dolby_audio)
                g_list_free(dolby_audio);
            if (audio_s)
                audio_s0 = g_string_free(audio_s, FALSE);
            else
                audio_s0 = g_strdup("0");
            if ((freeview || freesat) && lcn != last_lcn + 1)
                fprintf(fp, ":@%d\n", lcn);
            last_lcn = lcn;
            provider_name =
                    (const char *) sqlite3_column_text(chan_stmt, 7);
            if (provider_name && provider_name[0])
            {
                full_name = g_strdup_printf("%s;%s",
                        service_name, provider_name);
            }
            else
            {
                full_name = g_strdup(service_name);
            }
            fprintf(fp, "%s:%d:%s:%s:%d:%d:%s:%d:%d:%d:%d:%d:0\n",
                    full_name, freq, params,
                    freesat ? "S28.2E" : tuning_split[0],
                    srate,
                    vpid, audio_s0, tpid,
                    sqlite3_column_int(chan_stmt, 3),
                    service_id, sqlite3_column_int(chan_stmt, 5), ts_id);
            g_free(full_name);
            g_free(audio_s0);
        }
        else
        {
            if (dpid_undef)
                apid = dpid_undef;
            else if (apid_undef)
                apid = apid_undef;
            else if (dpid)
                apid = dpid;
            fprintf(fp, "%s:%d:%s:%d:%d:%d\n",
                    service_name, freq, params, vpid, apid, service_id);
        }
        g_strfreev(tuning_split);
        g_free(params);
    }
    fclose(fp);
    sqlite3_finalize(pid_stmt);
    sqlite3_finalize(chan_stmt);
    g_main_loop_quit(main_loop);
}

static void
get_next_transport_pids(PidHarvestData *phd);

static void
pmt_cb(gpointer handle, int reason, HdvbSection *sec, HdvbSectionFilter *sctf)
{
    PidHarvestData *phd = handle;
    PidHarvestTS *pht = phd->current->data;

    if (reason)
    {
        g_warning("Error in PMT filter: %s", strerror(reason));
    }
    else if (sec)
    {
        int service_id = HDVB_SECTION_PMT_SERVICE_ID(sec);
        GList *found = g_list_find(pht->services, GINT_TO_POINTER(service_id));
        GList *infos;
        GList *link;

        if (found)
        {
            infos = hdvb_section_list_pmt_info(sec);
            for (link = infos; link; link = g_list_next(link))
            {
                const HdvbPMTStreamInfo *si = link->data;
                int atype = 0;
                guint32 lang = 0;
                guint32 *lang_src = NULL;
                int stream_type = si->stream_type;

                if (!HDVB_STREAM_TYPE_IS_VIDEO(stream_type) &&
                        !HDVB_STREAM_TYPE_IS_AUDIO(stream_type) &&
                        stream_type != HDVB_STREAM_TYPE_PRIVATE_PES)
                {
                    continue;
                }
                if (HDVB_STREAM_TYPE_IS_AUDIO(stream_type) ||
                        stream_type == HDVB_STREAM_TYPE_PRIVATE_PES)
                {
                    GList *descriptors =
                            hdvb_section_list_pmt_stream_info_descriptors(si);
                    GList *dlink;

                    for (dlink = descriptors; dlink; dlink = g_list_next(dlink))
                    {
                        const HdvbDescriptor *desc = dlink->data;

                        switch (desc->h.tag)
                        {
                            case HDVB_DTAG_ISO639_LANGUAGE:
                            {
                                const HISO639LanguageDescriptor *adesc =
                                        dlink->data;

                                atype = adesc->d[0].audio_type;
                                lang_src = (guint32 *)
                                        adesc->d[0].iso_639_language_code;
                                break;
                            }
                            case HDVB_DTAG_TELETEXT:
                            {
                                const HdvbTeletextDescriptor *tdesc =
                                        dlink->data;

                                atype = HDVB_TELETEXT_DESCRIPTOR_TYPE(tdesc, 0);
                                lang_src = (guint32 *)
                                        tdesc->d[0].iso_639_language_code;
                                break;
                            }
                            case HDVB_DTAG_AC3:
                            case HDVB_DTAG_ENHANCED_AC3:
                                stream_type = HDVB_STREAM_TYPE_AC3_AUDIO;
                                break;
                            case HDVB_DTAG_DTS:
                            case HDVB_DTAG_AAC:
                                /* Not sure if ADTS == DTS let alone AAC,
                                 * but all we need to know is it's
                                 * "advanced" audio.
                                 */
                                stream_type = HDVB_STREAM_TYPE_ADTS_AUDIO;
                                break;
                        }
                    }
                }
                if (lang_src)
                {
                    char *clang = (char *) &lang;

                    lang = *lang_src;
                    clang[3] = 0;
                }
                sqlite3_bind_int(phd->stmt, 1, service_id);
                sqlite3_bind_int(phd->stmt, 2, pht->ts_id);
                sqlite3_bind_int(phd->stmt, 3,
                        HDVB_SECTION_PMT_STREAM_INFO_PID(si));
                sqlite3_bind_int(phd->stmt, 4, stream_type);
                sqlite3_bind_int(phd->stmt, 5, lang);
                sqlite3_bind_int(phd->stmt, 6, atype);
                hdvb_db_step(phd->stmt);
                sqlite3_reset(phd->stmt);
                sqlite3_clear_bindings(phd->stmt);
            }
            pht->services = g_list_delete_link(pht->services, found);
            --pht->n_services;
        }
    }
    if (sec)
        hdvb_section_free(sec);
    hdvb_section_filter_delete(sctf);
    if (!pht->n_services)
        get_next_transport_pids(phd);
}

static void
pat_cb(gpointer handle, int reason, HdvbSection *sec, HdvbSectionFilter *sctf)
{
    PidHarvestData *phd = handle;
    PidHarvestTS *pht = phd->current->data;

    pht->n_services = 0;
    if (reason)
    {
        g_warning("Error in PAT filter: %s", strerror(reason));
    }
    else if (sec)
    {
        int i;

        reason = 1;
        for (i = 0; i < HDVB_SECTION_DATA_LENGTH(sec); i += 4)
        {
            int sid = HDVB_SECTION_WORD(sec, i);
            int pid = HDVB_SECTION_WORD(sec, i + 2) & 0x1fff;

            if (g_list_find(pht->services, GINT_TO_POINTER(sid)))
            {
                ++pht->n_services;
                hdvb_section_filter_new(phd->rcv, pid, HDVB_TAB_PMT,
                        0xff, sid, 0xffff,
                        HDVB_SECTION_FILTER_TIMEOUT, pmt_cb,
                        FALSE, TRUE, FALSE, phd);
                reason = 0;
            }
        }
        if (reason)
            g_debug("No services of interest on '%s'", pht->tuning_str);
    }
    if (sec)
        hdvb_section_free(sec);
    hdvb_section_filter_delete(sctf);
    if (reason)
        get_next_transport_pids(phd);
}

static void
locked_for_pat(HdvbReceiver *rcv, gboolean locked, gpointer handle)
{
    PidHarvestData *phd = handle;
    PidHarvestTS *pht = phd->current->data;

    if (!locked)
    {
        g_warning("Timeout on '%s'", pht->tuning_str);
        get_next_transport_pids(phd);
        return;
    }
    hdvb_section_filter_new(phd->rcv, HDVB_PID_PAT, HDVB_TAB_PAT,
            0xff, 0, 0,
            HDVB_SECTION_FILTER_TIMEOUT, pat_cb,
            FALSE, TRUE, FALSE,
            phd);
}

static void
get_next_transport_pids(PidHarvestData *phd)
{
    PidHarvestTS *pht;

    if (phd->current)
        phd->current = g_list_next(phd->current);
    else
        phd->current = phd->transports;
    if (!phd->current)
    {
        print_channels(phd->db);
        g_main_loop_quit(main_loop);
        return;
    }
    pht = phd->current->data;
    if (hdvb_receiver_is_tuned_to(phd->rcv, pht->tuning_str))
    {
        locked_for_pat(phd->rcv, TRUE, phd);
    }
    else
    {
        HdvbTuningData td;
        int result;

        hdvb_tuning_string_to_data(pht->tuning_str, &td);
        result = hdvb_receiver_tune(phd->rcv, &td, pht->tuning_str, 10000);
        if (result)
        {
            g_warning("Failed to tune to '%s': %s", pht->tuning_str,
                    strerror(result));
            get_next_transport_pids(phd);
        }
    }
}

static void
get_pids(PidHarvestData *phd)
{
    PidHarvestTS *pht = NULL;
    sqlite3_stmt *stmt = hdvb_db_prepare_statement(phd->db,
            "SELECT s.service_id, s.ts_id, t.tuning_str"
            "   FROM services AS s, transports AS t"
            "   WHERE s.ts_id = t.ts_id"
            "   ORDER BY t.ts_id");

    if (!stmt)
        g_main_loop_quit(main_loop);
    phd->transports = NULL;
    phd->current = NULL;
    phd->stmt = hdvb_db_prepare_statement(phd->db,
            "INSERT OR REPLACE INTO pids"
            "   (service_id, ts_id, pid, stream_type, lang, audio_type)"
            "   VALUES (?, ?, ?, ?, ?, ?)");
    if (!phd->stmt)
        g_main_loop_quit(main_loop);
    while (hdvb_db_step(stmt) == SQLITE_ROW)
    {
        const char *tuning_str = (const char *) sqlite3_column_text(stmt, 2);

        if (!pht || strcmp(pht->tuning_str, tuning_str))
        {
            pht = g_slice_new(PidHarvestTS);
            pht->tuning_str = g_strdup(tuning_str);
            pht->ts_id = sqlite3_column_int(stmt, 1);
            pht->services = NULL;
            phd->transports = g_list_append(phd->transports, pht);
        }
        pht->services = g_list_append(pht->services,
                GINT_TO_POINTER(sqlite3_column_int(stmt, 0)));
    }
    sqlite3_finalize(stmt);
    g_debug("Getting PIDs for %d:%d", phd->rcv->adapter, phd->rcv->frontend);
    g_signal_connect(phd->rcv, "lock", G_CALLBACK(locked_for_pat), phd);
    get_next_transport_pids(phd);
}

static void
scan_cancelled_cb(HdvbChannelScan *scan, PidHarvestData *phd)
{
    g_main_loop_quit(main_loop);
}

static void
scan_complete_cb(HdvbChannelScan *scan, PidHarvestData *phd)
{
    int region_code = -1;
    int highest = 0;

    if (freesat)
    {
        if (region_name)
        {
            sqlite3_stmt *stmt = hdvb_db_prepare_statement(phd->db,
                    "SELECT code FROM region_codes WHERE name = ?");

            sqlite3_bind_text(stmt, 1, region_name, -1, SQLITE_STATIC);
            if (sqlite3_step(stmt) == SQLITE_ROW)
                region_code = sqlite3_column_int(stmt, 0);
            sqlite3_finalize(stmt);
        }
        if (region_code == -1)
        {
            /* No region given, use first one which should be London */
            sqlite3_stmt *stmt = hdvb_db_prepare_statement(phd->db,
                    "SELECT code, name FROM region_codes ORDER BY code");

            if (sqlite3_step(stmt) == SQLITE_ROW)
            {
                region_code = sqlite3_column_int(stmt, 0);
                region_name = g_strdup((const char *)
                        sqlite3_column_text(stmt, 1));
            }
            sqlite3_finalize(stmt);
        }
        g_debug("Using Freesat region %d: '%s'", region_code, region_name);
    }
    highest = hdvb_alloc_lcns(phd->db, region_code);
    if (highest)
    {
        highest += 100;
        highest -= highest % 100;
    }
    else
    {
        highest = 1;
    }
    hdvb_alloc_lcns_leftovers(phd->db, highest);

    if (dump_channels)
    {
        get_pids(phd);
    }
    else
    {
        g_message("Committing database to disc: %s", db_filename);
        hdvb_cached_db_sync(phd->cache);
        g_unlink(db_filename);
        g_rename(db_new_filename, db_filename);
        g_main_loop_quit(main_loop);
    }
}

int
main(int argc, char **argv)
{
    GError *error = NULL;
    GOptionContext *octx = g_option_context_new("INITIAL_TUNING_DATA_FILE");
    HdvbChanScanToDb *chan;
    GList *tuning_data;
    HdvbReceiver *rcv;
    PidHarvestData *phd;

    main_loop = g_main_loop_new(NULL, FALSE);
    g_option_context_add_main_entries(octx, entries, "hdvb-scan");
    if (!g_option_context_parse(octx, &argc, &argv, &error))
    {
        fprintf(stderr, "Option parsing failed: %s\n", error->message);
        exit(1);
    }
    if (argc != 2)
    {
        fprintf(stderr, "Incorrect arguments\n");
        exit(1);
    }
    if (!db_filename)
    {
        fprintf(stderr, "Output filename not specified");
        exit(1);
    }
    if (dump_vdr)
        dump_channels = TRUE;
    if (dump_channels)
    {
        dump_filename = db_filename;
        db_filename = (char *) ":memory:";
        db_new_filename = NULL;
    }
    else
    {
        db_new_filename = g_strconcat(db_filename, ".new", NULL);
        g_unlink(db_new_filename);
    }

    hdvb_receiver_udev_init(NULL, NULL);
    tuning_data = hdvb_channel_scan_parse_initial_tuning_file(argv[1]);
    if (!tuning_data)
        exit(1);
    rcv = hdvb_receiver_get_by_numbers(adapter, frontend);
    if (!rcv)
        exit(1);
    freesat = factory && !strcmp(factory, "Freesat");
    freeview = factory && !strcmp(factory, "Freeview");
    phd = g_new0(PidHarvestData, 1);
    phd->rcv = rcv;
    phd->cache = hdvb_cached_db_open(
            db_new_filename ? db_new_filename : db_filename,
            !dump_channels, FALSE,
            hdvb_chanscan_factory_lookup_db_creator(factory),
            HDVB_CHANSDB_VERSION, GINT_TO_POINTER(dump_channels));
    phd->db = phd->cache->db;
    chan = hdvb_chanscan_factory_lookup_chans_to_db(factory)
            (phd->db, factory);
    g_signal_connect(chan->scanner, "cancelled",
            G_CALLBACK(scan_cancelled_cb), phd);
    g_signal_connect(chan->scanner, "completed",
            G_CALLBACK(scan_complete_cb), phd);
    hdvb_channel_scan_start(chan->scanner, rcv, tuning_data);

    g_main_loop_run(main_loop);
    return 0;
}
